﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Brio.Framework
{
    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class Staff
    {
        // Takes in configuration file specifying:
        // Time Signature 
        // Bar
        public Staff(int measures, ScaleMask scaleMask, int[] timeSignature, int tempo)
        {
            // Set stuff
            this.measures = measures;
            this.timeSignature = timeSignature;
            this.scaleMask = scaleMask;
            this.tempo = tempo;
            this.techniqueLookupTable = new Dictionary<int, Tuple<string, int>>();
          
            // This equation will appeal to a 4-4 time signature, although it does not handle
            // others. I need to mathematically look into this
            double NoteRatio = TimeSignature[1] / TimeSignature[0];

            int staffSize = (int)(Measures * NoteRatio * Constants.SMALLEST_RHYTHMIC_UNIT);

            staff = new List<Note>(staffSize);

            int currentNoteValue = 0;

            // Fill staff with notes
            for (int i = 0; i < staffSize; i++)
            {
                int randomKeySetIndex = Constants.Random.Next(Scale.KeySet.Length);

                int gaussianKeySetIndex = (int)(NextBoxMuellerGaussian(0, 2) + Array.BinarySearch(Scale.KeySet, 60));

                int c4KeySetIndex = Array.BinarySearch(scaleMask.KeySet, 60);

                // Test simple rhythm
                if (i % (Constants.SMALLEST_RHYTHMIC_UNIT/1) == 0)
                {
                    currentNoteValue = Scale.KeySet[c4KeySetIndex];

                    staff.Add(new Note(NoteType.Note, currentNoteValue));
                }
                else
                {
                    staff.Add(new Note(NoteType.Hold, currentNoteValue));
                }
            }
  
            // Set rhythm
            ComputeNewRhythm();
        }

        public Note this[int index]
        {
            get { return staff[index]; }
        }

        public int Length
        {
            get { return staff.Count; }
        }

        private ScaleMask scaleMask;
        private int[] timeSignature;
        private int measures;
        private int[] rhythm;
        private int tempo;
        private List<Note> staff;
        public Dictionary<int, Tuple<string, int>> techniqueLookupTable;




        public int[] TimeSignature
        {
            get { return timeSignature; }
        }

        public int Measures
        {
            get { return measures; }
        }

        public ScaleMask Scale
        {
            get { return scaleMask; }
        }

        public int[] Rhythm
        {
            get { return rhythm; }
        }

        public int Tempo
        {
            get { return tempo; }
            set { tempo = value; }
        }

        public Dictionary<int, Tuple<string, int>> TechniqueLookupTable
        {
            get { return techniqueLookupTable; }
        }


        public int GetStaffIndexFromRhythmIndex(int rhythmIndex)
        {
            int staffIndex = 0;

            for (int i = 0; i < rhythmIndex; i++)
            {
                staffIndex += Rhythm[i];
            }

            return staffIndex;
        }



        public void MelodicSmoothing(int percentage, IntervalRelationshipTable intervalRelationships)
        {
            for (int i = 0; i < Rhythm.Length; i++)
            {
                // We want to do a mutation
                if (Constants.Random.Next(100) < percentage)
                {
                    // Smoothing is ~ O(n * m). 
                    
                    // Assumption : we only melodically want to smooth up to an octave.
                    // Obviously there are many exceptions but we will not explore this
                    // concept yet

                    int currentValue = 60 , currentIndex = 0;


                    // New
                    int noteValueAvg = 0, divideby = 0;
                    int lhsnotevalue = 0, rhsnotevalue = 0;
                    // Note Value Average
                    if (i > 0)
                    {
                        lhsnotevalue = staff[GetStaffIndexFromRhythmIndex(i - 1)].Value;
                        noteValueAvg += lhsnotevalue;
                        divideby++;
                    }

                    if (i != Rhythm.Length - 1)
                    {
                        rhsnotevalue = staff[GetStaffIndexFromRhythmIndex(i + 1)].Value;
                        noteValueAvg += rhsnotevalue;
                        divideby++;
                    }

                    noteValueAvg /= divideby;

                    for (int ks = 0; ks < Scale.KeySet.Length; ks++)
                    {
                        if (noteValueAvg < Scale.KeySet[ks])
                        {
                            if (ks == 0)
                            {
                                System.Windows.Forms.MessageBox.Show("Note below keyset bounds! Error");
                            }

                            currentIndex = ks - 1;
                            currentValue = Scale.KeySet[ks];
                            break;
                        }
                    }

                   






                    // Old 
                    //currentValue = staff[GetStaffIndexFromRhythmIndex(i)].Value;
                    //currentIndex = Array.BinarySearch(Scale.KeySet, currentValue);

                    int searchLowerBounds = -1, searchUpperBounds = -1;

                    int currentLowerValue = currentValue - 12;
                    int currentUpperValue = currentValue + 12;

                    // We want to find the index of the note that is an octave below the current.
                    // HOWEVER, if this is too low for our current keyset... then keep incrementing it by
                    // a halfstep till we find something acceptable, which is guaranteed to at least be the note.
                    while (searchLowerBounds < 0) // while the note is not in the keyset 
                    {
                        searchLowerBounds = Array.BinarySearch(Scale.KeySet, currentLowerValue);

                        currentLowerValue++;
                    }

                    // We want to find the index of the note that is an octave above the current.
                    // HOWEVER, if this is too high for our current keyset.... then keep decrementing it by
                    // a halfstep till we find something acceptable, which is guaranteed to at least be the note.

                    while (searchUpperBounds < 0) // while the note is not in the keyset 
                    {
                        searchUpperBounds = Array.BinarySearch(Scale.KeySet, currentUpperValue);

                        currentUpperValue--;
                    }

                    // Tuple<int,int> is equal to  (pointValue, newKeySetIndex)
                    List<Tuple<double, int>> intervalChoices = new List<Tuple<double, int>>();

                    // Search through the keyset from the lower bounds to the upper bounds
                    // and look to the left hand side (if there is one) and look up how many points that interval is
                    // then look to the right hand side (if there is one) and look up how many points that interval is
                    // add this combined value to a list
                    for (int j = searchLowerBounds; j < searchUpperBounds; j++)
                    {
                        double pointValue = 0;

                        // If there is a LHS Note Member
                        if (i > 0)
                        {
                            int lhsValue = staff[GetStaffIndexFromRhythmIndex(i-1)].Value;

                            int lhsInterval = Math.Abs(Scale.KeySet[j] - lhsValue);

                            double lhsPointValue = intervalRelationships[lhsInterval];

                            pointValue += lhsPointValue;
                        }
                        

                        // If there is a RHS Note Member
                        if (i != Rhythm.Length - 1)
                        {
                            int rhsValue = staff[GetStaffIndexFromRhythmIndex(i + 1)].Value;

                            int rhsInterval = Math.Abs(Scale.KeySet[j] - rhsValue);

                            double rhsPointValue = intervalRelationships[rhsInterval];

                            pointValue += rhsPointValue;
                        }

                        pointValue *= intervalRelationships.RelationshipMultiplier;

                        // Add the point value of the combined intervals and the searchIndex of the keyset
                        intervalChoices.Add(new Tuple<double, int>(pointValue, j)); 
                    }

                    // Roulette Wheel Select Interval
                    double sumPointValues = intervalChoices.Sum(sum => sum.Item1);

                    double randomNumber = Constants.Random.NextDouble() * sumPointValues;
                    double rouletteWheel = 0.0;

                    int newIndex = currentIndex;

                    for (int r = 0; r < intervalChoices.Count; r++)
                    {
                        rouletteWheel += intervalChoices[r].Item1;

                        if (randomNumber < rouletteWheel)
                        {
                            newIndex = intervalChoices[r].Item2;
                            break;
                        }
                    }

                    int staffIndex = GetStaffIndexFromRhythmIndex(i);

                    staff[staffIndex].Value = Scale.KeySet[newIndex];

                    // Mark these notes in accordance with our technique scheme
                    for (int k = 0; k < Rhythm[i]; k++)
                    {
                        staff[staffIndex + k].Marked = true;
                    }


                }
            }
        }


        // Combination
        // New Note, Point Value
        // New Note, Point Value






        public void GaussianNoteMutation(int percentage, int sigma)
        {

            for (int i = 0; i < Rhythm.Length; i++)
            {
                // We want to do a mutation
                if (Constants.Random.Next(100) < percentage)
                {
                    int mutationSteps = (int)Math.Floor(NextBoxMuellerGaussian(0, sigma));
                    int searchValue = staff[GetStaffIndexFromRhythmIndex(i)].Value;
                    int ksIndex = Array.BinarySearch(Scale.KeySet, searchValue);

                    if (ksIndex >= 0)
                    {
                        if (ksIndex + mutationSteps < Scale.KeySet.Length &&
                            ksIndex + mutationSteps >= 0)
                        {
                            int mutatedValue = Scale.KeySet[ksIndex + mutationSteps];
                            int staffIndex = GetStaffIndexFromRhythmIndex(i); 

                            staff[staffIndex].Value = mutatedValue;

                            // Mark these notes in accordance with our technique scheme
                            for (int j = 0; j < Rhythm[i]; j++)
                            {
                                staff[staffIndex + j].Marked = true;
                            }

                        }
                        else
                        {
                            // Out of range, just don't do anything
                        }
                    }
                    else
                    {
                        System.Windows.Forms.MessageBox.Show("SOMETHINGS UP WITH JACK!");
                    }
                }
            }

        }

        public double NextBoxMuellerGaussian(double mean, double stdDev)
        {
            double u1 = Constants.Random.NextDouble(); //these are uniform(0,1) random doubles
            double u2 = Constants.Random.NextDouble();
            double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) *
                         Math.Sin(2.0 * Math.PI * u2); //random normal(0,1)
            return mean + stdDev * randStdNormal; //random normal(mean,stdDev^2
        }

        public bool Join(int rhythmIndex)
        {
            // Rhythm is at maximum size, there is nothing to join it with
            // Joining is not possible!
            if (Rhythm.Length == 1)
                return false;

            // Rhthm index is the end index, cannot join with next note.
            if (rhythmIndex == Rhythm.Length - 1)
                return false;

            int joinLength = rhythm[rhythmIndex] + rhythm[rhythmIndex + 1];

            // Join (rhythmIndex) and (rhythmIndex + 1)
            staff[GetStaffIndexFromRhythmIndex(rhythmIndex + 1)].Type = NoteType.Hold;
            
            // Randomly choose which note stays
            Note LHS = staff[GetStaffIndexFromRhythmIndex(rhythmIndex)];
            Note RHS = staff[GetStaffIndexFromRhythmIndex(rhythmIndex + 1)];

            int choice = Constants.Random.Next(2); // 0 or 1

            
            staff[GetStaffIndexFromRhythmIndex(rhythmIndex)].Value = choice == 0 ? LHS.Value : RHS.Value;

            // Mark from starting index to ending index of join that a mutation happened
            int startIndex = GetStaffIndexFromRhythmIndex(rhythmIndex); 

            for (int i = 0; i < joinLength; i++)
            {
                staff[startIndex + i].Marked = true;
            }


            // Compute the new rhythm
            ComputeNewRhythm();

            return true;
        }

        public bool Split(int rhythmIndex)
        {
            // Rhythm is already at the smallest anotomical unit
            // Splitting is impossible.
            if (Rhythm[rhythmIndex] == 1)
                return false;
            
            // Everything is multiple of 2's because we are not worrying about
            // triplets. Hooray! Lovely lovely math.
            int splitIndex = Rhythm[rhythmIndex] / 2;


            // Split it in the middle
            staff[GetStaffIndexFromRhythmIndex(rhythmIndex) + splitIndex].Type = NoteType.Note;
            // Set Split note values equal
            staff[GetStaffIndexFromRhythmIndex(rhythmIndex) + splitIndex].Value = staff[GetStaffIndexFromRhythmIndex(rhythmIndex)].Value;

            // Mark from starting index to ending index of join that a mutation happened
            int startIndex = GetStaffIndexFromRhythmIndex(rhythmIndex);
            int rhythmLength = Rhythm[rhythmIndex];

            for (int i = 0; i < rhythmLength; i++)
            {
                staff[startIndex + i].Marked = true;
            }

            // Compute the new rhythm
            ComputeNewRhythm();

            return true;
        }


        private int currentUniqueID = 0;

        public int AddToLookupTable(Technique technique)
        {
            //////////////////////////////////////////////////////////////////
            // Unique ID      | Technique Name  |   Cardinality of Notes    //
            //////////////////////////////////////////////////////////////////
            //    0           |   ExampleName   |           10              //
            //   ...          |       ...       |           ...             //
            //////////////////////////////////////////////////////////////////
            techniqueLookupTable.Add(currentUniqueID, new Tuple<string, int>(technique.Name, technique.Rhythm.Count));
            
            // This returns currentUniqueID, then increments it right...? 
            return currentUniqueID++;
        }

        public void CleanupLookupTable()
        {
            // TODO: Look for techniques that have been COMPLETELY replaced in the staff, and then
            // remove them from the lookup table.
        }

        public bool Inject(Technique technique, int injectionLocation)
        {
            int divFactor = Constants.RHYTHM_WHOLE_VALUE / Constants.SMALLEST_RHYTHMIC_UNIT;
            int staffIndex = injectionLocation * (Constants.SMALLEST_RHYTHMIC_UNIT / 4);

            /*
            // Pre-injection boundary check
            int rhythmSum = 0;

            for (int i = 0; i < technique.Rhythm.Count; i++)
            {
                rhythmSum += technique.Rhythm[i] / divFactor;
            }
            */
            //if (staffIndex + rhythmSum > staff.Count)
            



            if (staffIndex + technique.Length > staff.Count)
                return false;


            // TODO: Find correct location for adding to look up table.
            // Add the technique to be injected into the look up table.
            int uniqueID = AddToLookupTable(technique);


            // Accepts limit functionality for techniques at the moment, will be
            // expanded if time permits.
            if (technique.RhythmType == RhythmTechniqueType.RhythmRelationship &&
                technique.NoteType == NoteTechniqueType.NoteRelationship)
            {
                
                for (int i = 0; i < technique.Rhythm.Count; i++)
                {
                    int noteLength = technique.Rhythm[i] / divFactor;

                    // First note is of a NoteType
                    staff[staffIndex].Type = NoteType.Note;
                    staff[staffIndex].Value = technique.Notes[i].Value;
                    staff[staffIndex].UniqueTechniqueID = uniqueID;
                    staff[staffIndex].Marked = false;

                    // Rest of notes are of HoldType
                    for (int j = 1; j < noteLength; j++)
                    {
                        staff[staffIndex + j].Type = NoteType.Hold;
                        //staff[staffIndex + j].Value = ?;
                        staff[staffIndex + j].UniqueTechniqueID = uniqueID;
                        staff[staffIndex + j].Marked = false;
                    }

                    staffIndex += noteLength;
                }

                // TODO: Needs improvement.
                if (staffIndex < staff.Count &&
                    staff[staffIndex].Type == NoteType.Hold)
                {
                    staff[staffIndex].Type = NoteType.Note;
                }

                ComputeNewRhythm();

                return true;
            }
            else
            {
                return false;
            }

        }


        public Staff Copy()
        {
            return HelperMethods.DeepClone<Staff>(this);
        }


        /// <summary>
        /// Compute new rhythm only when it changes.
        /// </summary>
        public void ComputeNewRhythm()
        {
            List<int> rhythmList = new List<int>();

            // 1 h h h 1 h 1 h

            int currentLength = 0;
            bool hasNote = false;

            for (int i = 0; i < staff.Count; i++)
            {
                if (staff[i].Type == NoteType.Note ||
                    staff[i].Type == NoteType.Rest)
                {
                    if (hasNote)
                    {
                        rhythmList.Add(currentLength);
                        currentLength = 0;
                    }

                    currentLength++;
                    hasNote = true;
                }
                else if (staff[i].Type == NoteType.Hold)
                {
                    currentLength++;
                }
            }

            rhythmList.Add(currentLength);

            rhythm = rhythmList.ToArray();
        }


        // if side == "LHS" then
        //  (this): XXXX1010
        //          ^^^^
        //          ||||
        //   doner: 1010XXXX
        // else
        //  (this): 1010XXXX
        //              ^^^^
        //              ||||
        //   doner: XXXX1010
        public void Crossover(MotifChromosome doner, int point, string side)
        {
            for (int i = 0; i < this.staff.Count; i++)
            {
                if(side.Equals("LHS"))
                {
                    if (i < point && doner.Staff[i] != null)
                    {
                        //set note value to doner's note value
                        staff[i].Value = doner.Staff[i].Value;
                        staff[i].Type = doner.Staff[i].Type;
                        if (doner.Staff[i].UniqueTechniqueID != -1 && techniqueLookupTable.ContainsKey(doner.Staff[i].UniqueTechniqueID))
                        {
                            techniqueLookupTable.Add(currentUniqueID, new Tuple<string, int>(techniqueLookupTable[doner.Staff[i].UniqueTechniqueID].Item1,
                                techniqueLookupTable[doner.Staff[i].UniqueTechniqueID].Item2));
                            staff[i].UniqueTechniqueID = currentUniqueID;
                            currentUniqueID++;
                        }

                        staff[i].Marked = doner.Staff[i].Marked;
                    }
                }
                else //side.Equals("RHS")
                {
                    if(i >= point && doner.Staff[i] != null)
                    {
                        //set note value to doner's note value
                        staff[i].Value = doner.Staff[i].Value;
                        staff[i].Type = doner.Staff[i].Type;
                        if (doner.Staff[i].UniqueTechniqueID != -1 && techniqueLookupTable.ContainsKey(doner.Staff[i].UniqueTechniqueID))
                        {
                            techniqueLookupTable.Add(currentUniqueID, new Tuple<string, int>(techniqueLookupTable[doner.Staff[i].UniqueTechniqueID].Item1,
                                techniqueLookupTable[doner.Staff[i].UniqueTechniqueID].Item2));
                            staff[i].UniqueTechniqueID = currentUniqueID;
                            currentUniqueID++;
                        }

                        staff[i].Marked = doner.Staff[i].Marked;
                    }
                }
            }

            this.ComputeNewRhythm();
        }
    }

    public enum InjectionType
    {
        NoteAndRhythm, Rhythm, Note
    }
}
